package _effx.greensock
{
   import boy.contrib.display.VectorField;
   
   import com.greensock.TweenLite;
   import com.greensock.plugins.TweenPlugin;
   
   import flash.display.Bitmap;
   import flash.display.BitmapData;
   import flash.display.Sprite;
   import flash.events.Event;
   import flash.filters.DisplacementMapFilter;
   import flash.filters.DisplacementMapFilterMode;
   import flash.geom.Matrix;
   import flash.geom.Point;
   import flash.geom.Rectangle;
   import flash.utils.Dictionary;

   /**
    * @author cjboy | cjboy1984@gmail.com
    * @version 1.1
    * @update 2011/04/12
    * @usage
    * // !!! RESTRICTION !!!
    * NOT sure if it works fine with scaleX and scaleY  - TODO : 克服scaleX & scaleY的issue
    *
    * // Mostly set blendMode = BlendMode.LAYER
    * blendMode = BlendMode.LAYER;
    * TweenPlugin.activate([JellyQPlugin]);
    *
    * // speed:0~100
    * TweenMax.to(mc, 1, { jellyQ:{start:0, end:100, speed:8, autoDestroy:false } } );
    * TweenMax.to(mc, 1, { delay:10, jellyQ:{start:100, end:0, speed:8 } } );
    */
   public class JellyQPlugin extends TweenPlugin
   {
      private const BMP_NAME:String = '_gs_jellyQ_';

      public static const VERSION:Number = 1.0;
      public static const API:Number = 1.0;

      private var _target:Sprite;
      // The effect bmp.
      private var _bitmap:Bitmap;
      // Perlin-Noise
      private var _perlinNoz:VectorField;
      private var _dispFilter:DisplacementMapFilter;
      // user given properties.
      private var _startValue:int = 0;
      private var _endValue:int = 30;
      private var _animationSpeed:Number = 10; // 0~100
      private var _autoDestroy:Boolean = true;
      // render function pointer
      private var _renderFunc:Function;

      // dictionaries
      private static var _nameDict:Dictionary = new Dictionary();
      private static var _perlinNozDict:Dictionary = new Dictionary();
      private static var _dispFilterDict:Dictionary = new Dictionary();
      private static var _renderFuncDict:Dictionary = new Dictionary();

      public function JellyQPlugin()
      {
         super();
         this.propName = "jellyQ";
         this.overwriteProps = ["jellyQ"];
         this.onComplete = onCompleteTween;
      }

      // ------------------ LINE -------------------

      override public function onInitTween(pTarget:Object, pValue:*, tween:TweenLite):Boolean
      {
         if (!(pTarget is Sprite))
         {
            trace("[Err] GreenSock-JellyQPlugin : It works only for Sprite");
            return false;
         }
         else if (typeof(pValue) != "object")
         {
            trace("[Err] GreenSock-JellyQPlugin : Parameter must be a object");
            return false;
         }

         _target = pTarget as Sprite;
         if (!_target.parent)
         {
            trace("[Err] GreenSock-JellyQPlugin : [" + _target.name + "]'s parent is undefined!");
            return false;
         }

         // init properties.
         _startValue = pValue.start;
         _endValue = pValue.end;
         _autoDestroy = (pValue.autoDestroy == undefined) ? true : pValue.autoDestroy ;
         _animationSpeed = pValue.speed;

         // find name from dictionary.
         var nname:String;
         if (_nameDict[_target] == undefined)
         {
            nname = BMP_NAME + _target.name;
            _nameDict[_target] = nname;
         }
         else
         {
            nname = _nameDict[_target] as String;
         }

         // find bmp from name.
         _bitmap = _target.parent.getChildByName(nname) as Bitmap;
         if (_bitmap == null)
         {
            _bitmap = new Bitmap();
            _bitmap.name = nname;
         }
         var targetIndex:int = _target.parent.getChildIndex(_target);
         _target.parent.addChildAt(_bitmap, targetIndex);
         _target.visible = false;

         // make perlinNoise, double size of width x height.
         if (_perlinNozDict[_target] == undefined)
         {
            _perlinNoz = new VectorField(_target.width << 1, _target.height << 1, true);
            decodeAnimationSpeed(_animationSpeed);

            _perlinNozDict[_target] = _perlinNoz;

            _perlinNoz.init();
         }
         else
         {
            _perlinNoz = _perlinNozDict[_target] as VectorField;

            _perlinNoz.pause();
            decodeAnimationSpeed(_animationSpeed);
            _perlinNoz.start();
         }

         // displacement map filter
         if (_dispFilterDict[_target] == undefined)
         {
            _dispFilter = new DisplacementMapFilter();
            _dispFilter.mapPoint = new Point( -_target.width >> 1, -_target.height >> 1);
            _dispFilter.componentX = 1;
            _dispFilter.componentY = 2;
            _dispFilter.mode = DisplacementMapFilterMode.COLOR;

            _dispFilterDict[_target] = _dispFilter;
         }
         else
         {
            _dispFilter = _dispFilterDict[_target] as DisplacementMapFilter;
         }

         // remove effect if target is removed from stage.
         _target.addEventListener(Event.REMOVED_FROM_STAGE, onRemove);

         return true;
      }

      // ------------------ LINE -------------------

      override public function killProps($lookup:Object):void
      {
         super.killProps($lookup);
         _autoDestroy = true;
         onCompleteTween();
      }

      // ------------------ LINE -------------------

      override public function get changeFactor():Number { return super.changeFactor; }
      override public function set changeFactor(value:Number):void
      {
         super.changeFactor = value;

         var scaleXY:Number = value * (_endValue - _startValue) + _startValue;
         _dispFilter.scaleX = scaleXY;
         _dispFilter.scaleY = scaleXY;

         onRenderEffect();
      }

      // ################ Protected ################

      // ################# Private #################

      private function onCompleteTween():void
      {
         // get render function.
         var renderFunc:Function;
         if (_renderFuncDict[_target] == undefined)
         {
            renderFunc = onRenderEffect;
            _renderFuncDict[_target] = renderFunc;
         }
         else
         {
            renderFunc = _renderFuncDict[_target] as Function;
         }

         // check whether to keep the JellyQ effect or remove it.
         if (_autoDestroy == false)
         {
            _target.addEventListener(Event.ENTER_FRAME, renderFunc);
            return;
         }

         _target.visible = true;
         _target.removeEventListener(Event.REMOVED_FROM_STAGE, onRemove);

         // name dictionary
         delete _nameDict[_target];

         // stop effect.
         delete _renderFuncDict[_target];
         _target.removeEventListener(Event.ENTER_FRAME, renderFunc);

         // stop PerlinNoise.
         delete _perlinNozDict[_target];
         if (_perlinNoz)
         {
            _perlinNoz.killAll();
            _perlinNoz = null;
         }

         // stop displacement filter.
         delete _dispFilterDict[_target];
         _dispFilter = null;

         // remove effect bmp.
         if (_target && _target.parent && _target.parent.contains(_bitmap))
         {
            _target.parent.removeChild(_bitmap);
         }

         // clear effect bmp.
         if(_bitmap)
         {
            if (_bitmap.bitmapData)
            {
               _bitmap.bitmapData.dispose();
               _bitmap.bitmapData = null;
            }

            _bitmap.filters = [];
            _bitmap = null;
         }
      }

      // ------------------ LINE -------------------

      private function onRenderEffect(e:Event = null):void
      {
         // get boundary rectangle relative to the coordinate system of _target.parent
         var targetParentBound:Rectangle = _target.getBounds(_target.parent);

         // to solve the animation of the target.
         var targetBound:Rectangle = _target.getBounds(_target);
         var _matrix:Matrix = new Matrix();
         _matrix.translate( -targetBound.x, -targetBound.y);
         _matrix.scale(_target.scaleX, _target.scaleY);

         _bitmap.bitmapData = new BitmapData(_target.width, _target.height, true, 0x00000000);
         _bitmap.bitmapData.draw(_target, _matrix);

         // TODO - Set all the proporties of _target to _bitmap, including filters, x, y, alpha, etc.
         _bitmap.alpha = _target.alpha;
         _bitmap.x = targetParentBound.x;
         _bitmap.y = targetParentBound.y;

         // apply effect
         _dispFilter.mapBitmap = _perlinNoz.bitmapData;
         _bitmap.filters = [_dispFilter];
      }

      // ------------------ LINE -------------------

      /**
       * Prevent the target from being removed from stage without killing its tween.
       */
      private function onRemove(e:Event):void
      {
         _target.removeEventListener(Event.REMOVED_FROM_STAGE, onRemove);

         _autoDestroy = true;
         onCompleteTween();
      }

      // ------------------ LINE -------------------

      private function decodeAnimationSpeed(v:Number):void
      {
         if (_animationSpeed < 0)
         {
            _animationSpeed = 0;
         }
         else if (_animationSpeed > 100)
         {
            _animationSpeed = 100;
         }

         _perlinNoz.channels = 3;
         _perlinNoz.numOctaves = 1;
         _perlinNoz.updateInterval = 15;
         _perlinNoz.animationSpeed = _animationSpeed / 10;
      }

      // ------------------ LINE -------------------

   }

}